/*
 * $Id: Events.java 3373 2008-05-12 16:21:24Z xlv $
 *
 * This code is part of the 'iText Tutorial'.
 * You can find the complete tutorial at the following address:

 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * itext-questions@lists.sourceforge.net
 */
package com.mxpdf.examples.directcontent.pageevents;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeSet;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import com.mxpdf.text.Chunk;
import com.mxpdf.text.Document;
import com.mxpdf.text.DocumentException;
import com.mxpdf.text.ElementTags;
import com.mxpdf.text.PageSize;
import com.mxpdf.text.Paragraph;
import com.mxpdf.text.Rectangle;
import com.mxpdf.text.TextElementArray;
import com.mxpdf.text.html.Markup;
import com.mxpdf.text.pdf.BaseFont;
import com.mxpdf.text.pdf.PdfContentByte;
import com.mxpdf.text.pdf.PdfPageEventHelper;
import com.mxpdf.text.pdf.PdfTemplate;
import com.mxpdf.text.pdf.PdfWriter;
import com.mxpdf.text.xml.SAXmyHandler;
import com.mxpdf.text.xml.TagMap;
import com.mxpdf.text.xml.XmlPeer;

/**
 * Example that takes an XML file, converts it to PDF and adds all kinds of
 * extra's, such as an alternating header, a footer with page x of y, a page
 * with metadata,...
 */
public class Events {

	/**
	 * This is an example of a PageEvents class you should write.
	 * This is an inner class to keep all the code of the example in one file.
	 * If you want to use a PageEvent, you may want to put the code in a separate class.
	 */

	class MyPageEvents extends PdfPageEventHelper {

		/** we will keep a list of speakers */
		TreeSet speakers = new TreeSet();

		/** This is the contentbyte object of the writer */
		PdfContentByte cb;

		/** we will put the final number of pages in a template */
		PdfTemplate template;

		/** this is the BaseFont we are going to use for the header / footer */
		BaseFont bf = null;

		/** this is the current act of the play */
		String act = "";
		
		/**
		 * Every speaker will be tagged, so that he can be added to the list of speakers.
		 * @see com.mxpdf.text.pdf.PdfPageEventHelper#onGenericTag(com.mxpdf.text.pdf.PdfWriter, com.mxpdf.text.Document, com.mxpdf.text.Rectangle, java.lang.String)
		 */
		public void onGenericTag(PdfWriter writer, Document document,
				Rectangle rect, String text) {
			speakers.add(new Speaker(text));
		}

		/**
		 * The first thing to do when the document is opened, is to define the BaseFont,
		 * get the Direct Content object and create the template that will hold the final
		 * number of pages.
		 * @see com.mxpdf.text.pdf.PdfPageEventHelper#onOpenDocument(com.mxpdf.text.pdf.PdfWriter, com.mxpdf.text.Document)
		 */
		public void onOpenDocument(PdfWriter writer, Document document) {
			try {
				bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252,
						BaseFont.NOT_EMBEDDED);
				cb = writer.getDirectContent();
				template = cb.createTemplate(50, 50);
			} catch (DocumentException de) {
			} catch (IOException ioe) {
			}
		}

		/**
		 * Every ACT is seen as a Chapter. We get the title of the act, so that
		 * we can display it in the header.
		 * @see com.mxpdf.text.pdf.PdfPageEventHelper#onChapter(com.mxpdf.text.pdf.PdfWriter, com.mxpdf.text.Document, float, com.mxpdf.text.Paragraph)
		 */
		public void onChapter(PdfWriter writer, Document document,
				float paragraphPosition, Paragraph title) {
			StringBuffer buf = new StringBuffer();
			for (Iterator i = title.getChunks().iterator(); i.hasNext();) {
				Chunk chunk = (Chunk) i.next();
				buf.append(chunk.getContent());
			}
			act = buf.toString();
		}

		/**
		 * After the content of the page is written, we put page X of Y
		 * at the bottom of the page and we add either "Romeo and Juliet"
		 * of the title of the current act as a header.
		 * @see com.mxpdf.text.pdf.PdfPageEventHelper#onEndPage(com.mxpdf.text.pdf.PdfWriter, com.mxpdf.text.Document)
		 */
		public void onEndPage(PdfWriter writer, Document document) {
			int pageN = writer.getPageNumber();
			String text = "Page " + pageN + " of ";
			float len = bf.getWidthPoint(text, 8);
			cb.beginText();
			cb.setFontAndSize(bf, 8);
			cb.setTextMatrix(280, 30);
			cb.showText(text);
			cb.endText();
			cb.addTemplate(template, 280 + len, 30);
			cb.beginText();
			cb.setFontAndSize(bf, 8);
			cb.setTextMatrix(280, 820);
			if (pageN % 2 == 1) {
				cb.showText("Romeo and Juliet");
			} else {
				cb.showText(act);
			}
			cb.endText();
		}

		/**
		 * Just before the document is closed, we add the final number of pages to
		 * the template.
		 * @see com.mxpdf.text.pdf.PdfPageEventHelper#onCloseDocument(com.mxpdf.text.pdf.PdfWriter, com.mxpdf.text.Document)
		 */
		public void onCloseDocument(PdfWriter writer, Document document) {
			template.beginText();
			template.setFontAndSize(bf, 8);
			template.showText(String.valueOf(writer.getPageNumber() - 1));
			template.endText();
		}

		/**
		 * Getting the list of speakers.
		 * @return	a list of speakers and the number of occurrences.
		 */
		public TreeSet getSpeakers() {
			return speakers;
		}
	}

	/**
	 * Gets a PageEvents object.
	 * @return a new PageEvents object
	 */
	public MyPageEvents getPageEvents() {
		return new MyPageEvents();
	}
	/**
	 * Gets a Handler object.
	 * @param document  the document on which the handler operates
	 * @return a Handler object
	 */
	public MyHandler getXmlHandler(Document document) {
		try {
			return new MyHandler(document, new RomeoJulietMap());
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * Converts a play in XML into PDF.
	 * @param args no arguments needed
	 */
	public static void main(String[] args) {

		System.out.println("Romeo and Juliet");

		// step 1: creation of a document-object
		Document document = new Document(PageSize.A4, 80, 50, 30, 65);

		try {
			// step 2:
			// we create a writer that listens to the document
			// and directs a XML-stream to a file
			PdfWriter writer = PdfWriter.getInstance(document,
					new FileOutputStream("RomeoJuliet.pdf"));

			// create add the event handler
			MyPageEvents events = new Events().getPageEvents();
			writer.setPageEvent(events);

			// step 3: we create a parser and set the document handler
			SAXParser parser = SAXParserFactory.newInstance().newSAXParser();

			// step 4: we parse the document
			parser.parse("playRomeoJuliet.xml", new Events().getXmlHandler(document));

			document.newPage();
			Speaker speaker;
			for (Iterator i = events.getSpeakers().iterator(); i.hasNext();) {
				speaker = (Speaker) i.next();
				document.add(new Paragraph(speaker.getName() + ": "
						+ speaker.getOccurrence() + " speech blocks"));
			}
			document.close();

		} catch (Exception e) {
			e.printStackTrace();
			System.err.println(e.getMessage());
		}
	}

	/**
	 * Special implementation of the XML handler.
	 * It adds a paragraph after each SPEAKER block and
	 * avoids closing the document after the final closing tag.
	 */
	class MyHandler extends SAXmyHandler {

		/**
		 * We have to override the constructor
		 * @param document	the Document object
		 * @param tagmap	the tagmap
		 */
		public MyHandler(Document document, HashMap tagmap) {
			super(document, tagmap);
		}

		/**
		 * We only alter the handling of some endtags.
		 * @param uri	the uri of the namespace
		 * @param lname	the local name of the tag
		 * @param name	the name of the tag
		 */
		public void endElement(String uri, String lname, String name) {
			if (myTags.containsKey(name)) {
				XmlPeer peer = (XmlPeer) myTags.get(name);
				// we don't want the document to be close
				// because we are going to add a page after the xml is parsed
				if (isDocumentRoot(peer.getTag())) {
					return;
				}
				handleEndingTags(peer.getTag());
				// we want to add a paragraph after the speaker chunk
				if ("SPEAKER".equals(name)) {
					try {
						TextElementArray previous = (TextElementArray) stack
								.pop();
						previous.add(new Paragraph(16));
						stack.push(previous);
					} catch (EmptyStackException ese) {
					}
				}
			} else {
				handleEndingTags(name);
			}
		}
	}

	/**
	 * Normally you either choose to use a HashMap with XmlPeer objects,
	 * or a TagMap object that reads a TagMap in XML.
	 * Here we used a hybrid solution (for educational purposes only!)
	 * with on one side the tags in the XML tagmap, on the other side
	 * an XmlPeer object that overrides the properties of one of the tags.
	 */

	class RomeoJulietMap extends TagMap {

		private static final long serialVersionUID = 1024517625414654121L;

		/**
		 * Constructs a TagMap based on an XML file
		 * and/or on XmlPeer objects that are added.
		 * @throws IOException
		 */
		public RomeoJulietMap() throws IOException {
			super(new FileInputStream("tagmapRomeoJuliet.xml"));
			XmlPeer peer = new XmlPeer(ElementTags.CHUNK, "SPEAKER");
			peer.addValue(Markup.CSS_KEY_FONTSIZE, "10");
			peer.addValue(Markup.CSS_KEY_FONTWEIGHT, Markup.CSS_VALUE_BOLD);
			peer.addValue(ElementTags.GENERICTAG, "");
			put(peer.getAlias(), peer);
		}
	}

	/**
	 * This object contains a speaker and a number of occurrences in the play
	 */

	class Speaker implements Comparable {

		// name of the speaker
		private String name;

		// number of occurrences
		private int occurrence = 1;

		/**
		 * One of the speakers in the play.
		 * @param name
		 */
		public Speaker(String name) {
			this.name = name;
		}

		/**
		 * Gets the name of the speaker.
		 * @return a name
		 */
		public String getName() {
			return name;
		}

		/**
		 * Gets the number of occurrences of the speaker.
		 * @return a number of textblocks
		 */
		public int getOccurrence() {
			return occurrence;
		}

		/**
		 * There is something odd going on in this compareTo.
		 * Do you see it?
		 * @param o an other speaker object
		 * @see java.lang.Comparable#compareTo(java.lang.Object)
		 */
		public int compareTo(Object o) {
			Speaker otherSpeaker = (Speaker) o;
			if (otherSpeaker.getName().equals(name)) {
				this.occurrence += otherSpeaker.getOccurrence();
				otherSpeaker.occurrence = this.occurrence;
				return 0;
			}
			return name.compareTo(otherSpeaker.getName());
		}
	}
}